/*
 *  blob.cpp
 *  bgsubtraction
 *
 *  Created by a1gucis on 4/24/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */

#include "blob.h"

Blob::Blob(ID *id) {
	this->id = id;
	this->topRow = -1;
	this->bottomRow = -1;
	this->leftCol = -1;
	this->rightCol = -1;
	this->adjacentBlobs = new vector<Blob *>();
	this->contourPixels = new vector<ContourPixel *>();
	this->fused = false;
	this->skinBlob = false;
	this->chan1 = 0;
	this->chan2 = 0;
	this->chan3 = 0;
	this->skinHits = 0;
}
	
Blob::~Blob() {
	adjacentBlobs->clear();
	delete adjacentBlobs;
	delete id;
}


int Blob::getArea() {
	return rowPixels.size();
}

int Blob::getTopRow() {
	return this->topRow;
}

int Blob::getBottomRow() {
	return this->bottomRow;
}

int Blob::getLeftCol() {
	return this->leftCol;
}

int Blob::getRightCol() {
	return this->rightCol;
}

float Blob::getBorderRatio(Blob *blob) {
	float hits = 0.0; 
	for (int i=0;i<contourPixels->size();i++) {
		ContourPixel *contourPixel = (*contourPixels)[i];
		if (contourPixel->getNeighbourID()->isEqual(blob->getID()))
			hits += 1.0;
	}
	float borderRatio = hits / contourPixels->size();
	return borderRatio;
}

int Blob::getCentroidRow() {
	return (this->topRow + this->bottomRow)/2;
}

int Blob::getCentroidCol() {
	return (this->leftCol + this->rightCol)/2;
}

int Blob::getLabel() {
	return this->label;
}

ID *Blob::getID() {
	return this->id;
}

double Blob::getChan1() {
	return this->chan1;
}

double Blob::getChan2() {
	return this->chan2;
}

double Blob::getChan3() {
	return this->chan3;
}

bool Blob::isFused() {
	return this->fused;
}

bool Blob::isSkinBlob() {
	//bool skinBlob = true;
	//float dist1 = (this->chan1 - SKIN_BLOB_MEAN_CHAN1);
	//dist1 *= dist1;
	//float dist2 = (this->chan2 - SKIN_BLOB_MEAN_CHAN2);
	//dist2 *= dist2;
	//float dist = dist1 + dist2;
	//if (dist < SKIN_BLOB_EUCLIDEAN_DIST)
	//	skinBlob = true;
	//if ((this->chan1 < SKIN_BLOB_MEAN_CHAN1 - SKIN_BLOB_VAR_CHAN1) || (this->chan1 > SKIN_BLOB_MEAN_CHAN1 + SKIN_BLOB_VAR_CHAN1))
	//	skinBlob = false;
	//if ((this->chan2 < SKIN_BLOB_MEAN_CHAN2 - SKIN_BLOB_VAR_CHAN2) || (this->chan2 > SKIN_BLOB_MEAN_CHAN2 + SKIN_BLOB_VAR_CHAN2))
	//	skinBlob = false;
	//if ((this->chan3 < SKIN_BLOB_MEAN_CHAN3 - SKIN_BLOB_VAR_CHAN3) || (this->chan3 > SKIN_BLOB_MEAN_CHAN3 + SKIN_BLOB_VAR_CHAN3))
	//	skinBlob = false;
	return this->skinBlob;
}

bool Blob::hasBackgroundAsNeighbour() {
	bool backgroundNeighbour = false;
	ID *bgID = new ID(0, 0, 1);
	for (int i=0;i<adjacentBlobs->size();i++) {
		Blob *neighbour = (*adjacentBlobs)[i];
		if (neighbour->getID()->isEqual(bgID))
			backgroundNeighbour = true;
	}
	delete bgID;	 
	return backgroundNeighbour;
}
vector<int>* Blob::getRowPixels() {
	return &this->rowPixels;
}

vector<int>* Blob::getColumnPixels() {
	return &this->columnPixels;
}

vector<ContourPixel *>* Blob::getContourPixels() {
	return this->contourPixels;
}

vector<Blob *> *Blob::getAdjacentBlobs() {
	return this->adjacentBlobs;
}

void Blob::addPixel(int row, int col) {
	rowPixels.push_back(row);
	columnPixels.push_back(col);
}

void Blob::addContourPixel(ContourPixel *contourPixel) {
	contourPixels->push_back(contourPixel);
}

void Blob::addNeighbour(Blob *neighbour){
	bool add = true;
	if (Blob::idsEqual(this, neighbour))
		add = false;
	else {
		for (int i=0;i<this->adjacentBlobs->size();i++) {
			Blob *blob = (*adjacentBlobs)[i];
			if (Blob::idsEqual(blob, neighbour))
				add = false;
		}
	}
	if (add) {
		this->adjacentBlobs->push_back(neighbour);
	}
}

void Blob::setLabel(int label) {
	this->label = label;
}

void Blob::setTopRow(int topRow) {
	this->topRow = topRow;
}

void Blob::setBottomRow(int bottomRow) {
	this->bottomRow = bottomRow;
}

void Blob::setLeftCol(int leftCol) {
	this->leftCol = leftCol;
}

void Blob::setRightCol(int rightCol) {
	this->rightCol = rightCol;
}

void Blob::setID(ID *id) {
	this->id = id;
}

void Blob::setFused() {
	this->fused = true;
}

void Blob::setSkin() {
	this->skinBlob = true;
}

void Blob::setContourPixels(vector<ContourPixel *> *contourPixels) {
	this->contourPixels = contourPixels;
}

void Blob::setMeanColor(double chan1, double chan2, double chan3) {
	this->chan1 = chan1;
	this->chan2 = chan2;
	this->chan3 = chan3;
}

void Blob::deleteNeighbour(Blob *neighbour) {
	bool deleted = false;
	for (int i=0;i<this->adjacentBlobs->size() && !deleted;i++) {
		Blob *blob = (*adjacentBlobs)[i];
		if (Blob::idsEqual(blob, neighbour)) {
			deleted = true;
			//do erasion from vector, without deleting the blob itself
			//for (int j=i;j<this->adjacentBlobs->size()-1;j++)
			//	(*adjacentBlobs)[j] = (*adjacentBlobs)[j+1];
			//adjacentBlobs->resize(adjacentBlobs->size()-1);
			//erase the blob from neighbours vector
			adjacentBlobs->erase(adjacentBlobs->begin()+i);
		}
	}
}

void Blob::printID() {
	this->id->printID();
}

void Blob::printMeanColor() {
	cout<<"chan1: "<<chan1<<", chan2: "<<chan2<<", chan3: "<<chan3;
}

void Blob::gatherBlobs(vector<Blob *> *blobVector, IplImage *labeledImage, int bgLabel, IDGenerator *generator, IplImage *originalImage, IplImage *blobImage) {
	IplImage *labeledImageCopy = cvCreateImage(cvGetSize(labeledImage), IPL_DEPTH_8U, 1);
	cvCopy(labeledImage, labeledImageCopy);
	uchar *data = (uchar *)labeledImageCopy->imageData;
	int oldLabel = bgLabel;
	for (int i=0;i<labeledImageCopy->height;i++) {
		for (int j=0;j<labeledImageCopy->width;j++) {
			int newLabel = data[i*labeledImageCopy->widthStep+j*labeledImageCopy->nChannels];
			if (newLabel != oldLabel) {
				oldLabel = newLabel;
				if ((newLabel != bgLabel) && (newLabel != BLOB_AREA)) {
					Blob *blob = Blob::createBlob(labeledImageCopy, i, j, generator->getNewID(), blobImage);
					blobVector->push_back(blob);
				}
			}
		}
	}
	
	for (int i=1;i<blobVector->size();i++) {
		Blob *blob = (*blobVector)[i];
		Blob::findMeanColorValues(blob, originalImage);
	}
	//gather contour pixels
	for (int i=1;i<blobVector->size();i++) {
		Blob *blob = (*blobVector)[i];
		vector<ContourPixel *> *contourPixels = new vector<ContourPixel *>();
		JarvisMarch::fi
		Blob::gatherContourPixels(blob, labeledImage, bgLabel, blobImage);
	}
	
	cvReleaseImage(&labeledImageCopy);
}

Blob* Blob::createBlob(IplImage *image, int row, int col, ID *id, IplImage *blobImage) {
	uchar *data = (uchar *)image->imageData;
	Blob *blob = new Blob(id);
	int label = data[row*image->widthStep+col*image->nChannels];
	blob->setLabel(label);
	Blob::findMainParameters(blob, image, row, col, blobImage);
	return blob;
}

void Blob::findMainParameters(Blob *blob, IplImage *image, int row, int col, IplImage *blobImage) {
	int topRow = row;
	int bottomRow = row;
	int leftCol = col, maxLeftCol = col;
	int rightCol = col, maxRightCol = col;
	int label = blob->getLabel();
	uchar *data = (uchar *)image->imageData;
	uchar *blobImageData = (uchar *)blobImage->imageData;
	bool terminate = false;
	while (!terminate) {
		terminate = true;
		//if ((row - topRow) == 1) // one iteration is being done
		//	col = (leftCol + rightCol) / 2;
		//leftCol = (maxLeftCol + maxRightCol) / 2;
		//leftCol = (leftCol + rightCol) / 2;
		leftCol = col;
		rightCol = leftCol+1;
		if (row < image->height) {
			//direction: left
			if (data[row*image->widthStep+leftCol*image->nChannels] == label)
				terminate = false;
			while (data[row*image->widthStep+leftCol*image->nChannels] == label) {
				blob->addPixel(row, leftCol);
				data[row*image->widthStep+leftCol*image->nChannels] = BLOB_AREA;
				blobImageData[row*blobImage->widthStep+leftCol*blobImage->nChannels+0] = blob->getID()->getR();
				blobImageData[row*blobImage->widthStep+leftCol*blobImage->nChannels+1] = blob->getID()->getG();
				blobImageData[row*blobImage->widthStep+leftCol*blobImage->nChannels+2] = blob->getID()->getB();
				leftCol--;
				if (leftCol < 0)
					break;
				if (leftCol < maxLeftCol)
					maxLeftCol = leftCol;
			}
			
			//direction: right
			if (rightCol < image->width) {
				if (data[row*image->widthStep+rightCol*image->nChannels] == label) {
					terminate = false;
					while (data[row*image->widthStep+rightCol*image->nChannels] == label) {
						blob->addPixel(row, rightCol);
						data[row*image->widthStep+rightCol*image->nChannels] = BLOB_AREA;
						blobImageData[row*blobImage->widthStep+rightCol*blobImage->nChannels+0] = blob->getID()->getR();
						blobImageData[row*blobImage->widthStep+rightCol*blobImage->nChannels+1] = blob->getID()->getG();
						blobImageData[row*blobImage->widthStep+rightCol*blobImage->nChannels+2] = blob->getID()->getB();						
						rightCol++;
						if (rightCol >= image->width)
							break;
						if (rightCol > maxRightCol)
							maxRightCol = rightCol;
					}
				}
			}
		}
		row++;
	}
	bottomRow = row - 1;
	blob->setTopRow(topRow);
	blob->setBottomRow(bottomRow);
	blob->setLeftCol(maxLeftCol);
	blob->setRightCol(maxRightCol);
}

void Blob::findMeanColorValues(Blob *blob, IplImage *originalImage) {
	//uchar *data = (uchar *)originalImage->imageData;
	float meanChan1 = 0;
	float meanChan2 = 0;
	float meanChan3 = 0;
	vector<int> *rowPixels = blob->getRowPixels();
	vector<int> *columnPixels = blob->getColumnPixels();
	for (int j=0;j<rowPixels->size();j++) {
		int row = (*rowPixels)[j];
		int col = (*columnPixels)[j];
		float chan1 = ((float *)(originalImage->imageData + row*originalImage->widthStep))[col*originalImage->nChannels + 0];
		float chan2 = ((float *)(originalImage->imageData + row*originalImage->widthStep))[col*originalImage->nChannels + 1];
		float chan3 = ((float *)(originalImage->imageData + row*originalImage->widthStep))[col*originalImage->nChannels + 2];
		meanChan1 += chan1;
		meanChan2 += chan2;
		meanChan3 += chan3;
	}
	meanChan1 = meanChan1 / (float)rowPixels->size();
	meanChan2 = meanChan2 / (float)rowPixels->size();
	meanChan3 = meanChan3 / (float)rowPixels->size();
	blob->setMeanColor(meanChan1, meanChan2, meanChan3);
}

void Blob::gatherContourPixels(Blob *blob, IplImage *image, int bgLabel, IplImage *blobImage) {
	ContourPixel *contourPixel;
	contourPixel = Blob::findContour(blob, image, blobImage, DIRECTION_LEFT, bgLabel);
	if (contourPixel != NULL)	
		blob->addContourPixel(contourPixel);
	
	contourPixel = Blob::findContour(blob, image, blobImage, DIRECTION_TOP_LEFT, bgLabel);
	if (contourPixel != NULL)	
		blob->addContourPixel(contourPixel);
	
	contourPixel = Blob::findContour(blob, image, blobImage, DIRECTION_TOP, bgLabel);
	if (contourPixel != NULL)	
		blob->addContourPixel(contourPixel);
	
	contourPixel = Blob::findContour(blob, image, blobImage, DIRECTION_TOP_RIGHT, bgLabel);
	if (contourPixel != NULL)	
		blob->addContourPixel(contourPixel);
	
	contourPixel = Blob::findContour(blob, image, blobImage, DIRECTION_RIGHT, bgLabel);
	if (contourPixel != NULL)	
		blob->addContourPixel(contourPixel);
	
	contourPixel = Blob::findContour(blob, image, blobImage, DIRECTION_BOTTOM_RIGHT, bgLabel);
	if (contourPixel != NULL)	
		blob->addContourPixel(contourPixel);
	
	contourPixel = Blob::findContour(blob, image, blobImage, DIRECTION_BOTTOM, bgLabel);
	if (contourPixel != NULL)	
		blob->addContourPixel(contourPixel);
	
	contourPixel = Blob::findContour(blob, image, blobImage, DIRECTION_BOTTOM_LEFT, bgLabel);
	if (contourPixel != NULL)	
		blob->addContourPixel(contourPixel);

}

ContourPixel* Blob::findContour(Blob *blob, IplImage *image, IplImage *blobImage, int direction, int bgLabel) {
	int row = blob->getCentroidRow();
	int col = blob->getCentroidCol();
	int prevRow = row;
	int prevCol = col;
	uchar *data = (uchar *)image->imageData;
	uchar *blobImageData = (uchar *)blobImage->imageData;
	bool terminate = false;
	ContourPixel *contourPixel = NULL;
	while (!terminate) {
		int label = (int)data[row*image->widthStep+col*image->nChannels];
		int r = blobImageData[row*blobImage->widthStep+col*blobImage->nChannels+0];
		int g = blobImageData[row*blobImage->widthStep+col*blobImage->nChannels+1];
		int b = blobImageData[row*blobImage->widthStep+col*blobImage->nChannels+2];
		if (label == bgLabel) {
			contourPixel = new ContourPixel(prevRow, prevCol, new ID(r, g, b));
			terminate = true;
		}
		else if ((r != blob->getID()->getR()) || (g != blob->getID()->getG()) || (b != blob->getID()->getB())) {
			contourPixel = new ContourPixel(prevRow, prevCol, new ID(r, g, b));
			terminate = true;
		}
		else {
			prevRow = row;
			prevCol = col;
		}
		switch(direction) {
			case DIRECTION_LEFT:
				col += -1;
				break;
			case DIRECTION_TOP_LEFT:
				row += -1;
				col += - 1;
				break;
			case DIRECTION_TOP:
				row += -1;
				break;
			case DIRECTION_TOP_RIGHT:
				row += -1;
				col += 1;
				break;
			case DIRECTION_RIGHT:
				col += 1;
				break;
			case DIRECTION_BOTTOM_RIGHT:
				row += 1;
				col += 1;
				break;
			case DIRECTION_BOTTOM:
				row += 1;
				break;
			case DIRECTION_BOTTOM_LEFT:
				row += 1;
				col += -1;
				break;
		}
		if ((row < 0) || (row >= image->height))
			break;
		if ((col < 0) || (col >= image->width))
			break;
	}
	return contourPixel;	
}

void Blob::gatherNeighbours(vector<Blob *> *blobVector, IplImage *labeledImage, int bgLabel, IplImage *blobImage) {
	for (int i=1;i<blobVector->size();i++) {
		Blob *blob = (*blobVector)[i];
		Blob *neighbour = Blob::findNeighbour(blob, blobVector, labeledImage, bgLabel, DIRECTION_LEFT, blobImage);
		if (neighbour != NULL)
			blob->addNeighbour(neighbour);
		
		neighbour = Blob::findNeighbour(blob, blobVector, labeledImage, bgLabel, DIRECTION_TOP_LEFT, blobImage);
		if (neighbour != NULL)
			blob->addNeighbour(neighbour);
		
		neighbour = Blob::findNeighbour(blob, blobVector, labeledImage, bgLabel, DIRECTION_TOP, blobImage);
		if (neighbour != NULL)
			blob->addNeighbour(neighbour);
			
		neighbour = Blob::findNeighbour(blob, blobVector, labeledImage, bgLabel, DIRECTION_TOP_RIGHT, blobImage);
		if (neighbour != NULL)
			blob->addNeighbour(neighbour);
			
		neighbour = Blob::findNeighbour(blob, blobVector, labeledImage, bgLabel, DIRECTION_RIGHT, blobImage);
		if (neighbour != NULL)
			blob->addNeighbour(neighbour);

		neighbour = Blob::findNeighbour(blob, blobVector, labeledImage, bgLabel, DIRECTION_BOTTOM_RIGHT, blobImage);
		if (neighbour != NULL)
			blob->addNeighbour(neighbour);
			
		neighbour = Blob::findNeighbour(blob, blobVector, labeledImage, bgLabel, DIRECTION_BOTTOM, blobImage);
		if (neighbour != NULL) 
			blob->addNeighbour(neighbour);
		
		neighbour = Blob::findNeighbour(blob, blobVector, labeledImage, bgLabel, DIRECTION_BOTTOM_LEFT, blobImage);
		if (neighbour != NULL)
			blob->addNeighbour(neighbour);
		
		
	}
	Blob::calibrateNeighbours(blobVector, bgLabel);
}

Blob* Blob::findNeighbour(Blob *blob, vector<Blob *> *blobVector, IplImage *labeledImage, int bgLabel, int direction, IplImage *blobImage) {
	uchar *data = (uchar *)labeledImage->imageData;
	uchar *blobImageData = (uchar *)blobImage->imageData;
	int row = blob->getCentroidRow();
	int col = blob->getCentroidCol();
	Blob *neighbour = NULL;
	bool terminate = false;
	
	while (!terminate) {
		int label = (int)data[row*labeledImage->widthStep+col*labeledImage->nChannels];
		//neighbour blob is a background blob
		if (label == bgLabel) {
			neighbour = (*blobVector)[0];
			terminate = true;
		} else {
			int r = (int)blobImageData[row*blobImage->widthStep+col*blobImage->nChannels+0];
			int g = (int)blobImageData[row*blobImage->widthStep+col*blobImage->nChannels+1];
			int b = (int)blobImageData[row*blobImage->widthStep+col*blobImage->nChannels+2];
			if ((r != blob->getID()->getR()) || (g != blob->getID()->getG()) || (b != blob->getID()->getB())) {
				terminate = true;
				neighbour = Blob::findBlob(r, g, b, blobVector);
			}
			
		}
				
		switch(direction) {
			case DIRECTION_LEFT:
				col += -1;
				break;
			case DIRECTION_TOP_LEFT:
				row += -1;
				col += - 1;
				break;
			case DIRECTION_TOP:
				row += -1;
				break;
			case DIRECTION_TOP_RIGHT:
				row += -1;
				col += 1;
				break;
			case DIRECTION_RIGHT:
				col += 1;
				break;
			case DIRECTION_BOTTOM_RIGHT:
				row += 1;
				col += 1;
				break;
			case DIRECTION_BOTTOM:
				row += 1;
				break;
			case DIRECTION_BOTTOM_LEFT:
				row += 1;
				col += -1;
				break;
		}
		if ((row < 0) || (row >= labeledImage->height))
			break;
		if ((col < 0) || (col >= labeledImage->width))
			break;
	}
	return neighbour;
}

void Blob::calibrateNeighbours(vector<Blob *> *blobVector, int bgLabel) {
	for (int i=0;i<blobVector->size();i++) {
		Blob *blob = (*blobVector)[i];
		vector<Blob *> *neighboursVector = blob->getAdjacentBlobs();
		for (int j=0;j<neighboursVector->size();j++) {
			Blob *neighbourBlob = (*neighboursVector)[j];
			neighbourBlob->addNeighbour(blob);
		}
	}
}

void Blob::doBlobFusion(vector<Blob *> **blobVector, IplImage *originalImage, int fgArea, int bgLabel) {
	//Blob::fuseBlobsWithOneNeighbour(blobVector);
	bool firstTimeCall = true;
	bool totalChangesFound = true;
	while (totalChangesFound) {
		totalChangesFound = false;
		bool changesFound = true;
		while (changesFound) {
			changesFound = false;
			changesFound = fuseBlobsWithSameLabel(blobVector, bgLabel);
			
			//search for skin Blob
			//if (firstTimeCall) {
			//	firstTimeCall = false;
				for (int i=0;i<(*blobVector)->size();i++) {
					Blob *blob = (**blobVector)[i];
					checkForSkinBlob(blob, originalImage);
				}
			//}
			
			//calculate the covariance matrix between blob colors
			CvMat** vectorArray = new CvMat*[(*blobVector)->size()-1];
			for (int i=1;i<(*blobVector)->size();i++) {
				Blob *blob = (**blobVector)[i];
				CvMat *mat = cvCreateMat(1, 3, CV_32FC1); //1 row, 3 columns
				cvmSet(mat, 0, 0, blob->getChan1());
				cvmSet(mat, 0, 1, blob->getChan2());
				cvmSet(mat, 0, 2, blob->getChan3());
				(vectorArray)[i-1] = mat;
			}
			CvMat *covarMatrix = cvCreateMat(3, 3, CV_32FC1);
			CvMat *meanMatrix = cvCreateMat(1, 3, CV_32FC1);
			cvCalcCovarMatrix((const CvArr **)vectorArray, (*blobVector)->size()-1, covarMatrix, meanMatrix, CV_COVAR_NORMAL | CV_COVAR_SCALE);
			cvInvert(covarMatrix, covarMatrix, CV_SVD);
			changesFound = Blob::fuseBlobsWithLargeBorderRatioAndSimilarColor(blobVector, covarMatrix);
			//changesFound = false;
			//release the vectorArray
			for (int i=0;i<(*blobVector)->size()-1;i++) {
				CvMat *mat = vectorArray[i];
				cvReleaseMat(&mat);
			}
			delete vectorArray;
			cvReleaseMat(&covarMatrix);
			cvReleaseMat(&meanMatrix);
		}
	Blob::fuseSmallBlobs(blobVector, fgArea, bgLabel);
		//totalChangesFound = fuseSmallBlobs(blobVector, fgArea, bgLabel);
	}

}

bool Blob::fuseBlobsWithSameLabel(vector<Blob *> **blobVector, int bgLabel) {
	bool fusionsPerformed = false;
	for (int i=0;i<(*blobVector)->size();i++) {
		Blob *blob = (**blobVector)[i];
		if (!blob->isFused()) {
			vector<Blob *> *blobNeighbours = blob->getAdjacentBlobs();
			for (int j=0;j<blobNeighbours->size();j++) {
				Blob *neighbour = (*blobNeighbours)[j];
				if (blob->getLabel() == neighbour->getLabel()) {
					Blob::fuseBlobs(blob, neighbour);
					fusionsPerformed = true;
				}
			}
		}
	}
	
	//create new blobVector and delete the old one
	if (fusionsPerformed) {
		vector<Blob *> *newBlobVector = new vector<Blob *>();
		for (int i=0;i<(*blobVector)->size();i++) {
			Blob *blob = (**blobVector)[i];
			if (!blob->isFused()) { //if blob is not fused
				newBlobVector->push_back(blob);
			}
		}
		(*blobVector)->clear();
		delete *blobVector;
		*blobVector = newBlobVector;
	}
	return fusionsPerformed;
}

bool Blob::fuseBlobsWithLargeBorderRatioAndSimilarColor(vector<Blob *> **blobVector, CvMat *inverseCovarianceMatrix) {
	bool fusionsPerformed = false;
	for (int i=1;i<(*blobVector)->size();i++) {
		Blob *blob = (**(blobVector))[i];
		if (!blob->isFused() && !blob->isSkinBlob()) {
			vector<Blob *> *neighbours = blob->getAdjacentBlobs();
			for (int j=0;j<neighbours->size();j++) {
				Blob *neighbour = (*neighbours)[j];
				if (!neighbour->isSkinBlob()) {
					if ((neighbour->getBorderRatio(blob) >= BORDER_RATIO) && (neighbour->getArea() < blob->getArea())) {
						//cout<<"border ratio is similar "<<endl;
						if (colorIsSimilar(blob, neighbour, inverseCovarianceMatrix)) {
							//bool skin = false;
							//if (blob->isSkinBlob()) {
							//	skin = true;
							//	cout<<"fusing to skin blob: "<<blob->getChan1()<<" "<<blob->getChan2()<<" "<<blob->getChan3()<<endl;
							//}
							fuseBlobs(blob, neighbour);
							//if (skin)
							//	cout<<"after fusion blob: "<<blob->getChan1()<<" "<<blob->getChan2()<<" "<<blob->getChan3()<<endl;
							fusionsPerformed = true;
						} //else if (blob->getBorderRatio(neighbour) >= BORDER_RATIO) {
					}		//fuseBlobs(blob, neighbour);
						//fusionsPerformed = true;
					//}
				}
				//} else if ((blob->getBorderRatio(neighbour) >= BORDER_RATIO) && (colorIsSimilar(neighbour, blob, inverseCovarianceMatrix))) {
				//	fuseBlobs(neighbour, blob);
				//	fusionsPerformed = true;
				//}
			}
		}
	}
	if (fusionsPerformed) {
		vector<Blob *> *newBlobVector = new vector<Blob *>();
		for (int i=0;i<(*blobVector)->size();i++) {
			Blob *blob = (**blobVector)[i];
			if (!blob->isFused()) { //if blob is not fused
				newBlobVector->push_back(blob);
			}
		}
		(*blobVector)->clear();
		delete *blobVector;
		*blobVector = newBlobVector;
	}
	return fusionsPerformed;
	
}

bool Blob::fuseSmallBlobs(vector<Blob *> **blobVector, int fgArea, int bgLabel) {
	bool fusionsPerformed = false;
	for (int i=1;i<(*blobVector)->size();i++) {
		Blob *blob = (**blobVector)[i];
		if (!blob->isFused()) {
			vector<Blob *> *neighbours = blob->getAdjacentBlobs();
			//if (!blob->isSkinBlob()) {
				if (blob->getArea() <= (float)fgArea*GLOBALLY_SMALL_BLOB) {
					if (neighbours->size() == 0) // if the blob does not have neighbours
						fuseBlobs((**blobVector)[0], blob);
					else {
						int maxPos = 0;
						int maxSize = -1;
						for (int j=0;j<neighbours->size();j++) {
							Blob *neighbour = (*neighbours)[j];
							if (neighbour->getArea() > maxSize) {
								maxSize = neighbour->getArea();
								maxPos = j;
							}
						}
						fuseBlobs((*neighbours)[maxPos], blob);

					}
					fusionsPerformed = true;
				}
			//}
		}
	}
	//create new blobVector and delete the old one
	if (fusionsPerformed) {
		vector<Blob *> *newBlobVector = new vector<Blob *>();
		for (int i=0;i<(*blobVector)->size();i++) {
			Blob *blob = (**blobVector)[i];
			if (!blob->isFused()) { //if blob is not fused
				newBlobVector->push_back(blob);
			}
		}
		(*blobVector)->clear();
		delete *blobVector;
		*blobVector = newBlobVector;
	}
	return fusionsPerformed;
}

void Blob::fuseBlobsWithOneNeighbour(vector<Blob *> **blobVector) {
	bool fusionsPerformed = false;
	for (int i=0;i<(*blobVector)->size();i++) {
		Blob *blob = (**blobVector)[i];
		if (!blob->isFused()) {
			vector <Blob *> *neighbours = blob->getAdjacentBlobs();
			if (neighbours->size() == 1) {
				fuseBlobs((*neighbours)[0], blob);
				fusionsPerformed = true;
			}

		}
	}
	if (fusionsPerformed) {
		vector<Blob *> *newBlobVector = new vector<Blob *>();
		for (int i=0;i<(*blobVector)->size();i++) {
			Blob *blob = (**blobVector)[i];
			if (!blob->isFused()) { //if blob is not fused
				newBlobVector->push_back(blob);
			}
		}
		(*blobVector)->clear();
		delete *blobVector;
		*blobVector = newBlobVector;
	}
	
	
}

void Blob::fuseBlobs(Blob *blob1, Blob *blob2) {
	//set blobs2 flag fused to true
	blob2->setFused();
	
	//recalculate the mean color values for blob1
	if (!blob1->isSkinBlob()) {
		int blob1Area = blob1->getArea();
		int blob2Area = blob2->getArea();
		double mixCof1 = (double) blob1Area / (double)(blob1Area + blob2Area);
		double mixCof2 = (double) blob2Area / (double)(blob1Area + blob2Area);
		
		double meanChan1 = mixCof1*blob1->getChan1() + mixCof2*blob2->getChan1();
		double meanChan2 = mixCof1*blob1->getChan2() + mixCof2*blob2->getChan2();
		double meanChan3 = mixCof1*blob1->getChan3() + mixCof2*blob2->getChan3();
		blob1->setMeanColor(meanChan1, meanChan2, meanChan3);
	}
	
	
	//add blob2 rowPixels and columnPixels to blob1
	vector<int> *blob2RowPixels = blob2->getRowPixels();
	vector<int> *blob2ColumnPixels = blob2->getColumnPixels();
	for (int i=0;i<blob2RowPixels->size();i++) {
		int row = (*blob2RowPixels)[i];
		int col = (*blob2ColumnPixels)[i];
		blob1->addPixel(row, col);
	}
	
	//recalculate the centroid values for blob1
	if (blob2->getTopRow() < blob1->getTopRow()) 
		blob1->setTopRow(blob2->getTopRow());
	if (blob2->getBottomRow() > blob1->getBottomRow())
		blob1->setBottomRow(blob2->getBottomRow());
	if (blob2->getLeftCol() < blob1->getLeftCol())
		blob1->setLeftCol(blob2->getLeftCol());
	if (blob2->getRightCol() > blob1->getRightCol())
		blob1->setRightCol(blob2->getRightCol());
	

		
	//recalculate the contour pixels for blob 1
	vector <ContourPixel *> *blob1ContourPixels = blob1->getContourPixels();
	vector <ContourPixel *> *newBlob1ContourPixels = new vector<ContourPixel *>();
	for (int i=0;i<blob1ContourPixels->size();i++) {
		ContourPixel *contourPixel = (*blob1ContourPixels)[i];
		if (!contourPixel->getNeighbourID()->isEqual(blob2->getID())) 
			newBlob1ContourPixels->push_back(contourPixel);
	}
	blob1ContourPixels->clear();
	delete blob1ContourPixels;
	
	//recalculate the contour pixels for blob 2
	vector <ContourPixel *> *blob2ContourPixels = blob2->getContourPixels();
	vector <ContourPixel *> *newBlob2ContourPixels = new vector<ContourPixel *>();
	for (int i=0;i<blob2ContourPixels->size();i++) {
		ContourPixel *contourPixel = (*blob2ContourPixels)[i];
		if (!contourPixel->getNeighbourID()->isEqual(blob1->getID())) 
			newBlob2ContourPixels->push_back(contourPixel);
	}
	
	//add blob2 contour pixels to blob1 contour Pixels
	for (int i=0;i<newBlob2ContourPixels->size();i++) {
		ContourPixel *contourPixel = (*newBlob2ContourPixels)[i];
		newBlob1ContourPixels->push_back(contourPixel);
	}
	newBlob2ContourPixels->clear();
	delete newBlob2ContourPixels;
	blob1->setContourPixels(newBlob1ContourPixels);
		
	
	//add blob2 neighbours to blob1 neighbour list
	vector<Blob *> *blob2Neighbours = blob2->getAdjacentBlobs();
	for (int i=0;i<blob2Neighbours->size();i++) {
		Blob *blob2Neighbour = (*blob2Neighbours)[i];
		blob1->addNeighbour(blob2Neighbour);
	}
	
	//change blob2 neighbours contour pixel ids to blob1 id
	for (int i=0;i<blob2Neighbours->size();i++) {
		Blob *blob2Neighbour = (*blob2Neighbours)[i];
		vector<ContourPixel *> *contourPixels = blob2Neighbour->getContourPixels();
		for (int j=0;j<contourPixels->size();j++) {
			ContourPixel *contourPixel = (*contourPixels)[j];
			if (contourPixel->getNeighbourID()->isEqual(blob2->getID()))
				contourPixel->setNeighbourID(blob1->getID());
		}
	}
	
	//replace each blob, having blob2 as a neighbour with blob1 as a neighbour
	for (int i=0;i<blob2Neighbours->size();i++) {
		Blob *blob2Neighbour = (*blob2Neighbours)[i];
		blob2Neighbour->deleteNeighbour(blob2);
		blob2Neighbour->addNeighbour(blob1);
	}
}

bool Blob::idsEqual(Blob *firstBlob, Blob *secondBlob) {
	bool equal = false;
	ID *firstID = firstBlob->getID();
	ID *secondID = secondBlob->getID();
	
	if ((firstID->getR()== secondID->getR()) && (firstID->getG() == secondID->getG()) && (firstID->getB() == secondID->getB()))
		equal = true;
	return equal;
}


bool Blob::colorIsSimilar(Blob *firstBlob, Blob *secondBlob, CvMat *inverseCovarianceMatrix) {
	bool colorSimilar = false;
	CvMat *firstBlobColor = cvCreateMat(1, 3, CV_32FC1);
	cvmSet(firstBlobColor, 0, 0, firstBlob->getChan1());
	cvmSet(firstBlobColor, 0, 1, firstBlob->getChan2());
	cvmSet(firstBlobColor, 0, 2, firstBlob->getChan3());
		
	CvMat *secondBlobColor = cvCreateMat(1, 3, CV_32FC1);
	cvmSet(secondBlobColor, 0, 0, secondBlob->getChan1());
	cvmSet(secondBlobColor, 0, 1, secondBlob->getChan2());
	cvmSet(secondBlobColor, 0, 2, secondBlob->getChan3());
	double distance = cvMahalanobis(firstBlobColor, secondBlobColor, inverseCovarianceMatrix);
	//cout<<"mahalanobis distance: "<<distance<<endl;
	if (distance <= COLOR_DISTANCE_THRESHOLD)
		colorSimilar = true;
	else
		cout<<"mahalanobis distance: "<<distance<<endl;
	cvReleaseMat(&firstBlobColor);
	cvReleaseMat(&secondBlobColor);
	return colorSimilar;
}

Blob* Blob::findBlob(int r, int g, int b, vector<Blob *> *blobVector) {
	Blob *found = NULL;
	for (int i=0;i<blobVector->size();i++) {
		Blob *blob = (*blobVector)[i];
		if ((blob->getID()->getR() == r) && (blob->getID()->getG() == g) && (blob->getID()->getB() == b)) {
			found = blob;
			break;
		}
	}
	return found;
}

int Blob::findBlobPos(Blob *blob, vector<Blob *> *blobVector) {
	int found = false;
	int pos = -1;
	for (int i=0;i<blobVector->size() && !found;i++) {
		Blob *nextBlob = (*blobVector)[i];
		if (idsEqual(blob, nextBlob)) {
			found = true;
			pos = i;
		}
	}
	return pos;
}

void Blob::checkForSkinBlob(Blob *blob, IplImage *originalImage) {
	blob->skinHits = 0;
	vector<int> *rows = blob->getRowPixels();
	vector<int> *cols = blob->getColumnPixels();
	int skinPixelsFound = 0;
	for (int z=0;z<rows->size();z++) {
		int row = (*rows)[z];
		int col = (*cols)[z];
		float chan1 = ((float *)(originalImage->imageData + row*originalImage->widthStep))[col*originalImage->nChannels + 0];
		float chan2 = ((float *)(originalImage->imageData + row*originalImage->widthStep))[col*originalImage->nChannels + 1];
		if (isSkinColor(chan1, chan2)) {
			skinPixelsFound++;
			blob->skinHits++;
		}
			//cout<<"skin pixel calculated from: "<<chan1<<" "<<chan2<<" "<<chan3<<endl		
	}
	if (skinPixelsFound > SKIN_BLOB_PIXELS_TO_FIND)
		blob->setSkin();
}

bool Blob::isSkinColor(float chan1, float chan2) {
	bool skinColor = false;
	float dist1 = chan1-SKIN_BLOB_MEAN_CHAN1;
	dist1 *= dist1;
	float dist2 = chan2-SKIN_BLOB_MEAN_CHAN2;
	dist2 *= dist2;
	float dist = dist1 + dist2;
	if (dist < SKIN_BLOB_EUCLIDEAN_DIST) {
		skinColor = true;
		//cout<<"skin dist: "<<dist<<"|||chan1:"<<chan1<<" chan2:"<<chan2<<endl;
	}
	return skinColor;
}